package org.lightpersist.util;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.lightcommons.util.Assert;
import org.lightcommons.util.PackageUtils;
import org.lightpersist.annotations.Dynamic;

import com.google.inject.Binder;
import com.google.inject.cglib.proxy.Proxy;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;

/**
 * Utility class for GUICE
 * @author GL
 *
 */
public class GuiceUtils {

	public static void bindDynamicInterceptor(Binder binder ,String basePackage,Matcher<? super Class<?>> typeMatcher,Matcher<? super Method> methodMatcher,MethodInterceptor... interceptors){
		bindDynamicInterceptor(binder, PackageUtils.getClasses(basePackage), typeMatcher, methodMatcher, interceptors);
	}
	
	/**
	 * bind Interceptors ,it can also bind to Interface and AbstractClasses,it means ,you could write code only in interceptors.
	 * @param binder
	 * @param dynamicClasses interfaces and abstract classes
	 * @param typeMatcher
	 * @param methodMatcher
	 * @param interceptors
	 */
	@SuppressWarnings("unchecked")
	public static void bindDynamicInterceptor(Binder binder,Collection<Class<?>> dynamicClasses,Matcher<? super Class<?>> typeMatcher,Matcher<? super Method> methodMatcher,MethodInterceptor... interceptors){
//		binder.bindInterceptor(typeMatcher, methodMatcher, interceptors);
		if(dynamicClasses==null)
			return;
		for(Class cls : dynamicClasses){
			Object instance = buildDynamicObject(cls, typeMatcher, methodMatcher, interceptors);
			if(instance!=null){
				binder.bind(cls).toInstance(instance);
			}
		}
	}
	
	public static Object buildDynamicObject(Class<?> clazz,Matcher<? super Class<?>> typeMatcher,Matcher<? super Method> methodMatcher,MethodInterceptor[] interceptors){
		Dynamic dynamic = clazz.getAnnotation(Dynamic.class);
		if(dynamic!=null){
			Class<?> useClass = clazz;
			if(dynamic.value()!=Dynamic.class)
				useClass = dynamic.value();
			if(useClass.isInterface()){
				return Proxy.newProxyInstance(useClass.getClassLoader(),
                        new Class<?>[] { useClass }, new AopAllianceJdkProxyAdapter(typeMatcher,methodMatcher,interceptors));
			}else/* if(Modifier.isAbstract(useClass.getModifiers())) */ {
				return com.google.inject.cglib.proxy.Enhancer.create(useClass,
                        new AopAllianceCglibAdapter(typeMatcher,methodMatcher,interceptors));
			}
		}
		//throw new LightCommonsException(clazz.getName()+" is not annotated with @Dynamic");
		return null;
	}
	
	public static List<Class<?>>  findClasses(String packageName,Matcher<? super Class> typeMatcher){
		List<Class<?>> clazzes = new ArrayList<Class<?>>();
		for(Class<?> clazz:PackageUtils.getClasses(packageName)){
			if(typeMatcher.matches(clazz)){
				clazzes.add(clazz);
			}
		}
		return clazzes;
	}
	
	  public static Matcher<Class> inSubpackages(final String... targetPackageNames) {
		    return new InSubpackages(targetPackageNames);
		  }

		  private static class InSubpackages extends AbstractMatcher<Class> implements Serializable {
		    private final String[] targetPackageNames;

		    public InSubpackages(String... targetPackageNames) {
		    	Assert.notEmpty(targetPackageNames, "targetPackageName can't be empty!");
		    	this.targetPackageNames = targetPackageNames;
		    }

		    public boolean matches(Class c) {
		      String classPackageName = c.getPackage().getName();
		      return cycleMatches(classPackageName, 0);
		    }
		    
		    private boolean cycleMatches(String classPackageName,int i){
		    	return classPackageName.equals(targetPackageNames[i])
		          || classPackageName.startsWith(targetPackageNames[i] + ".") || (i<targetPackageNames.length-1 && cycleMatches(classPackageName, ++i));
		    }

		    @Override public boolean equals(Object other) {
		      return other instanceof InSubpackage
		          && ((InSubpackages) other).targetPackageNames.equals(targetPackageNames);
		    }

		    @Override public int hashCode() {
		      return 37 * targetPackageNames.hashCode();
		    }

		    @Override public String toString() {
		      return "inSubpackage(" + targetPackageNames+ ")";
		    }

		    private static final long serialVersionUID = 0;
		  }

	  /**
	   * Returns a matcher which matches classes in the given package and its subpackages. Unlike
	   * {@link #inPackage(Package) inPackage()}, this matches classes from any classloader.
	   * 
	   * @since 2.0
	   */
	  public static Matcher<Class> inSubpackage(final String targetPackageName) {
	    return new InSubpackage(targetPackageName);
	  }

	  private static class InSubpackage extends AbstractMatcher<Class> implements Serializable {
	    private final String targetPackageName;

	    public InSubpackage(String targetPackageName) {
	      this.targetPackageName = targetPackageName;
	    }

	    public boolean matches(Class c) {
	      String classPackageName = c.getPackage().getName();
	      return classPackageName.equals(targetPackageName)
	          || classPackageName.startsWith(targetPackageName + ".");
	    }

	    @Override public boolean equals(Object other) {
	      return other instanceof InSubpackage
	          && ((InSubpackage) other).targetPackageName.equals(targetPackageName);
	    }

	    @Override public int hashCode() {
	      return 37 * targetPackageName.hashCode();
	    }

	    @Override public String toString() {
	      return "inSubpackage(" + targetPackageName + ")";
	    }

	    private static final long serialVersionUID = 0;
	  }

}
